Skip to content

[vcpkg.targets] Skip applocal when the installed bin directory does not exist - #54330

Merged
Billy O'Neal (BillyONeal) merged 2 commits into
microsoft:masterfrom
azchohfi:applocal-skip-without-installed-bin
Oct 6, 2026
Merged

Billy O'Neal (BillyONeal) merged 2 commits into
microsoft:masterfrom
azchohfi:applocal-skip-without-installed-bin

Conversation

@azchohfi

Copy link
Copy Markdown
Contributor

AppLocalFromInstalled runs after every link and starts vcpkg.exe z-applocal to copy DLLs from
installed\<triplet>\[debug\]bin next to the binary. When that directory does not exist, there is
nothing to copy, but the process still starts. With the user-wide integration
(vcpkg integrate install), this happens in every C++ project built with MSBuild on the machine,
including ones that use no vcpkg packages. CMake's Visual Studio generator alone builds two such
projects in every fresh configure: the compiler-ID project and the ABI try_compile.

This adds one condition to the target:

   <Target Name="AppLocalFromInstalled" AfterTargets="CopyFilesToOutputDirectory" BeforeTargets="CopyLocalFilesOutputGroup;RegisterOutput"
-          Condition="'$(_ZVcpkgClassicOrManifest)' == 'true' and '$(VcpkgApplocalDeps)' == 'true' and '$(LinkSkippedExecution)' != 'true' and '@(Link)' != ''">
+          Condition="'$(_ZVcpkgClassicOrManifest)' == 'true' and '$(VcpkgApplocalDeps)' == 'true' and '$(LinkSkippedExecution)' != 'true' and '@(Link)' != ''
+                     and Exists('$(_ZVcpkgCurrentInstalledDir)$(_ZVcpkgConfigSubdir)bin')">

The path is the same one the target already passes as --installed-bin-dir.

Manifest mode

MSBuild evaluates a target's Condition when the target is about to run, not when the project is
loaded. In manifest mode, VcpkgInstallManifestDependencies (BeforeTargets="ClCompile") has
created vcpkg_installed\<triplet>\... by then, so the check sees it. Tested below, on a project
whose vcpkg_installed did not exist before the build.

Testing

Each case is a full -t:Rebuild of a real linked C++ project, CMake's generated
CompilerIdCXX.vcxproj, at -v:d. It records whether AppLocalFromInstalled ran and what it
copied. Integration from this branch; vcpkg-tool 2026-09-26.

mode case AppLocalFromInstalled DLL deployed
classic no installed\ tree, without this change runs (z-applocal, copies nothing) —
classic no installed\ tree, with this change skipped —
classic the bin directory the target resolves for this project (installed\x64-windows\bin) holds a DLL the binary imports runs yes
manifest dependency on an overlay port that installs a DLL into bin and debug\bin; vcpkg_installed absent before the build runs, after the install step yes
manifest no dependencies (vcpkg_installed created, no bin) skipped —

No CI or test in this repository covers scripts/buildsystems/msbuild. git diff --check is
clean. Not tested: x86/arm64 triplets, static triplets, vcpkg export --nuget packages,
Release configuration.

Numbers

CMake's Visual Studio 18 generator on a two-file C++ project with the user-wide integration and no
installed\ tree. Each pass runs every case once, in rotating order; values are medians in ms,
and the brackets are the 95% bootstrap CI of the per-pass paired difference. Windows 11, i9-14900K,
MSBuild 18.10, CMake 4.4.3.

cmake -S . -B build / cmake --build build no integration master master + this change saved by this change
configure, Dev Drive 2,082 2,424 2,079 242 [192, 480]
build, Dev Drive 2,110 2,232 2,105 137 [−16, 278]
configure, C: (NTFS) 2,509 2,799 2,471 360 [197, 542]
build, C: 2,091 2,358 2,216 187 [13, 281]
  • This run. Integration from master 9b4c015 (2026-10-05); 12 passes, on a shared machine
    (load canary 1.17 before, 1.12 after).
  • An earlier run on a quiet machine. Integration from a9d71f3 (2026-10-03), whose
    scripts/buildsystems/msbuild files are byte-identical to master's; 11 passes:
    • configure: 184 ms saved [139, 263];
    • build: 93 ms saved [55, 120];
    • with the change, both are within noise of no integration: configure +41 [−19, 109], build
      +1 [−20, 90].

Context

#52315 made z-applocal the default instead of pwsh applocal.ps1, which removed most of this
cost (about 1.2 s per configure on a checkout from 2025). What remains is starting vcpkg.exe
once per linked project when there is nothing to deploy.

…ot exist

AppLocalFromInstalled starts vcpkg.exe z-applocal after every link, to copy
DLLs from installed\<triplet>\[debug\]bin next to the binary. When that
directory does not exist, there is nothing it could copy. This happens with
the user-wide integration (vcpkg integrate install) in every C++ project
that uses no vcpkg packages, including the compiler-identification and
try_compile projects that CMake's Visual Studio generator builds.

Add Exists() on that directory to the target's condition. Target conditions
are evaluated when the target is about to run, so in manifest mode the
directory created by VcpkgInstallManifestDependencies earlier in the same
build is seen.
Copilot AI balanced review requested due to automatic review settings October 6, 2026 04:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The focused condition uses the existing app-local path and is supported by relevant manual testing.

Review effort: Balanced
Findings: None

What changed in this PR

Avoids unnecessary MSBuild app-local deployment when the selected vcpkg binary directory is absent.

Changes:

  • Adds an execution-time directory existence check to AppLocalFromInstalled.
  • Preserves manifest-mode behavior after dependency installation.
File Description
scripts/​buildsystems/​msbuild/​vcpkg.targets Skips app-local deployment when no installed bin directory exists.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Pre-existing, reported by the PR whitespace check when the file is edited.

@BillyONeal Billy O'Neal (BillyONeal) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the analysis and fix!

@BillyONeal
Billy O'Neal (BillyONeal) merged commit 001713e into microsoft:master Oct 6, 2026
16 checks passed
@azchohfi
Alexandre Zollinger Chohfi (azchohfi) deleted the applocal-skip-without-installed-bin branch October 6, 2026 18:38
Billy O'Neal (BillyONeal) added a commit to BillyONeal/vcpkg that referenced this pull request Oct 6, 2026
VcpkgInstallManifestDependencies strips inherited MSBuild globals from the install child, but RemoveProperties takes precedence over Properties. When VcpkgAdditionalInstallOptions or VcpkgTriplet is supplied with /p:, it appears in both lists and is removed despite being explicitly forwarded. Project-local options work, but command-line overlay flags are lost and overlay-only dependencies fail to resolve.

Define the forwarded property names and values once, exclude those names from the removal list, and escape all forwarded values consistently. Unrelated inherited globals remain isolated from the install child.

This pre-existing problem was originally detected by GPT 6.1 Sol while reviewing microsoft#54330. It was not introduced by that PR. Regression coverage lives in the integrate-install-properties end-to-end suite in vcpkg-tool.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@azchohfi

Copy link
Copy Markdown
Contributor Author

Measured on two public CMake projects:

  • Projects: fmt 11.2.0 (FMT_TEST=ON) and GLFW 3.4 (examples and tests on).
  • Toolchain: Visual Studio 18 generator, Debug, x64. cmake --build with no --parallel, as most scripts run it, so one MSBuild node.
  • Integration: user-wide, as vcpkg integrate install writes it, with no installed\ tree. Three arms, interleaved in every pass:
    • no integration;
    • this repository's master (9b4c015);
    • master + this PR.
  • Passes: 6 per arm. Each is a fresh configure followed by a full build. Windows 11, i9-14900K, Dev Drive.
fmt GLFW
linked projects (exe/dll) in the build 22 31
z-applocal runs in the build, master 22 31
z-applocal runs in the build, this PR 0 0
build, no integration 50.3 s 17.3 s
build, master 53.1 s 23.8 s
build, this PR 50.4 s 17.4 s
saved by this PR (paired median, 95% CI) 2.5 s (−0.9 to 6.6) 4.6 s (3.7 to 10.2)
saved, as a share of the master build 4.6% 19.3%
passes where this PR was faster 5 of 6 6 of 6
saving per linked project ~110 ms ~150 ms
  • With this PR, both builds are back at the no-integration time: +0.1 s on each.
  • The cost scales with the number of linked projects. It is about 0.1–0.15 s per exe or DLL: one vcpkg.exe start that finds nothing to copy.
  • Its weight depends on how long each project takes to compile. In fmt, compilation dominates, so the saving is about 5% and within noise on 6 passes. In GLFW, the projects are small C programs, so it is about a fifth of the build.
  • Configure builds two linked projects of its own (compiler ID and ABI check). The saving there is about 0.3 s on both projects, within noise at this sample size. An earlier 11-pass A/B on a smaller project measured 184 ms saved per configure (CI 139–263).

Not measured: --parallel builds, where the per-project cost is spread across nodes; Release; and C: drives.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants